#include <string.h>
#include <unistd.h>
#include <stdio.h>

#include "libavutil/avutil.h"
#include "libavutil/imgutils.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include <libswscale/swscale.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>

/*
	AVPacket srcPkt;	 
    AVPacket *destPkt = av_packet_alloc();		//av_frame_alloc 只是为结构体分配空间
	AVFrame *decFrame =   av_frame_alloc( ) ; 			
	AVFrame *pFrame420p =  av_frame_alloc( ) ; 
	
	char *out_buffer= av_malloc(  图像大小 ); 
	av_image_fill_arrays(pFrame420p->data,pFrame420p->linesize,out_buffer, AV_PIX_FMT_YUV420P,  width,  height,  1);    // 给pFrame420p记录 这片数据空间
	
	while(   ( ret = av_read_frame(ifmt_ctx, &srcPkt)  )  == 0  ){    //pkt自动分配,要unref
         ret = avcodec_send_packet(decodec_ctx,&srcPkt);   //送入解码器
         ret = avcodec_receive_frame(decodec_ctx,  destFrame); //自动分配,自动释放
         sws_scale(sws_ctx,(const unsigned char* const*)destFrame->data,
                   destFrame->linesize, 0,  decodec_ctx->height, pFrame420p->data,
                          pFrame420p->linesize);    //pFrame420p必须手工分配空间
		 
          ret = avcodec_send_frame(enc_ctx,   pFrame420p);  送入编码器
          while(ret>=0){		//大概只循环几次而已
                  ret = avcodec_receive_packet(enc_ctx, destPkt);      //自动为newpkt分配,要unref
                  为newpkt指定 时间基,pts,dts,duratioin, 时间基转换为ofmt_ctx的
                  ret = av_interleaved_write_frame(ofmt_ctx, destPkt);  发送出去
                  av_packet_unref(destPkt);
           }
           av_packet_unref(&srcPkt); 
    }


		数据格式流:
		AVPacket  srcPkt;  摄像头采集原始图像 yuv422p	,AVPacket格式用来存放压缩流,也可以存放裸数据拉流
						av_read_frame(ifmt_ctx, &srcPkt)从输入设备读取数据.
							数据空间自动分配,用完 自己释放#####
		AVFrame *destFrame;  	srcPkt送去解码,得到 destFrame,内部也是 yuv422
						avcodec_send_packet(decodec_ctx,&srcPkt);		解码
						avcodec_receive_frame(decodec_ctx,  destFrame);	得到
							destFrame 空间自动分配,不要释放,方便下次使用####.
							
		AVFrame *pFrame420p;	destFrame是422p,h264要使用420p.
					需要转换器转换				sws_scale(sws_ctx,destFrame,pFrame420p);
						pFrame420p自己没有数据空间,需要专门函数分配空间, 不要释放方便继续使用
					送去编码:		 ret = avcodec_send_frame(enc_ctx, pFrame420p);

		
		AVPacket *destPkt:
				pFrame420p 经过编码,得到的. ret = avcodec_receive_packet(enc_ctx, destPkt);
							自动分配destPkt空间,记得要释放
				需要发出去:			ret = av_interleaved_write_frame(ofmt_ctx, destPkt);
*/


#define  av_error_output( des,  ret)  { \
	char av_error[256] = { '\0' };av_strerror(ret, av_error, 256);\
	printf("%s-%d %s %s\n",__func__,__LINE__,des,av_error);\
}


#define V_WIDTH 	  640			//发现切换到720p fps只有10帧
#define V_HEIGHT 	  480
#define	V_HZ		   30


char *url_rtsp = "rtsp://127.0.0.1:8554/stream";


static AVFormatContext *ifmt_ctx_create(void) 
{
    int ret = 0;
	char cmd[128] = {0};					//通过字符串设置命令  
    AVFormatContext *ifmt_ctx = NULL;
    AVDictionary *options = NULL;			  // 包含字符串的指令,用于存储设置的参数
    char *devicename = "/dev/video0";		   //摄像头的设备文件

	/*执行硬件模块初始化动作,比如摄像头,麦克风,之后可以使用硬件了 ,前提设备驱动都是OK的*/
    avdevice_register_all();	

	/*	AVFormatContext *avformat_alloc_context(void);
		创建 输入/输出上下文,分配空间并设置默认值;
			后续还需要指定 设备/文件 视频的格式 具体参数等
			如果想释放空间:		void avformat_free_context(AVFormatContext *ctx) 
	*/
	ifmt_ctx = avformat_alloc_context();
	/*	获取输入源的视频格式:
		AVInputFormat *av_find_input_format(const char *short_name);
		根据输入的名字,解析视频格式信息,主要是后缀.mp4 .avi ,还可以是linux摄像头:video4linux2
		根据输入的信息,从ffmpeg中搜索对应的 格式,填充到AVInputFormat 格式中,然后返回给你.
			输入的格式必须是ff已经支持的.
			下面是一个ffmpeg内部的 inputFormat对象
				AVInputFormat ff_avfoundation_demuxer = {
				    .name           = "MPEG-4",		视频格式名称,下一个是长名字
				    .long_name      = NULL_IF_CONFIG_SMALL("AVFoundation input device"),
				    .priv_data_size = sizeof(AVFContext),
				    .read_header    = avf_read_header,
				    .read_packet    = avf_read_packet,
				    .read_close     = avf_close,
				    .flags          = AVFMT_NOFILE,	表示  格式是否为文件/流媒体,文件还需要打开,创建,关闭等操作
				    .priv_class     = &avf_class,
				};
	*/

	/*设置摄像头格式,必须是 硬件已经支持的 ...	*/
    const AVInputFormat *iformat = av_find_input_format("video4linux2");
	/*上面已经知道输入设备的视频格式了,下面开始设置 分辨率 帧率 数据格式等
		将你想要的格式填充到 AVDictionary *options结构中;
		主要是通过 辅助函数av_dict_set完成.

		av_dict_set完成的功能还有很多,异常的丰富.
	*/
	snprintf(cmd,sizeof(cmd),"%dx%d",V_WIDTH,V_HEIGHT);
    av_dict_set(&options, "video_size", cmd, 0);
    av_dict_set(&options, "framerate", "30", 0);
    av_dict_set(&options, "pixel_format", "yuyv422", 0);
	
	/* 
		第一步中,创建的 ifmt_ctx上下文,统管整个输入设备;但是还没有对他进行初始化
			接下来 指定 设备名字/文件名, 输入格式, 视频参数等选项进行初始化;
			以后,只需要使用ifmt_ctx即可.
	*/
    ret = avformat_open_input(&ifmt_ctx, devicename, iformat, &options);
    if (ret < 0) {
      	av_error_output("open input err",ret);
        return NULL;
    }
	/*探测probe 输入设备/文件的 流信息,存入 ifmt_ctx->stream[]中.
		它的原理很简单,读取 设备/文件的 头部,或者几帧数据,分析

		ifmt设备/文件,可能包含多个stream,比如 高级的 usb摄像头还具有mic的功能;
			ifmt_ctx->stream[0/1/2]				后续我们都是从某个stream中获取 数据.
			每个流stream[i] 都包含了 当前stream信息,比如
				id,编解码格式(h264 raw ),帧率,分辨率,图像格式等等.

				AVStream{	存储流媒体信息
					int index;	该媒体的id,编号 0 1 2 3 ...
					AVRational time_base;	时基,转换 PTS DTS,单位sec
					AVRational avg_frame_rate;	帧率,对视频重要
					AVPacket attached_pic;	附带的图片,比如专辑封面
					AVCodecParameters  codecpar { 音视频参数,用来替换  上面启用的cdec
						enum AVMediaType codec_type // 媒体类型（视频AVMEDIA_TYPE_VIDEO，音频AVMEDIA_TYPE_AUDIO...）   
						enum AVCodecID codec_id // ###标示特定的编解码器（H.264,MPEG,raw ...）
						int format // 视频#像素#格式/音频采样数据格式
						int width, height // 表示视频的宽和高
						int bit_rate // 平均比特率    
						int channels // 声道数（音频）
						uint64_t channel_layout // 声道格式
						int sample_rate // 采样率（音频）
						AVRational time_base; // 时基。通过该值可以把PTS，DTS转化为实际的时间（单位为秒s）
					};
				}	
	*/
	ret = avformat_find_stream_info(ifmt_ctx, NULL);
	if (0 != ret){
		av_error_output("find stream err", ret);
		return 0;
	}

	/*
		自此,ifmt_ctx 拥有了输入设备的一切信息.

		AVFormatContext {
			char filename[] ,保存的文件名/设备名, url[rtsp rtmp]等
			int nb_streams; 	流个数
			AVStream *streams[];  #####存放流的	地方
			int64	duaration ; 	媒体时长,us
			int64	bit_rate;	比特率bps,  转换为kbps要除以 1000
			
			struct AVInputFormat *iformat;		//输入流信息 
			struct AVOutputFormat *oformat; 	//输出流信息
			void *priv_data ; 存放一些私有设置	
		}

		打印出来看看吧:

		av_dump_format(输入输出的上下文结构, 打印第几个流呢, 设备/文件, 输入0-输出1);
	*/
	printf("------ ifmt print----\n");
	av_dump_format(ifmt_ctx, 0, devicename, 0);
    return ifmt_ctx;
}


/*
	创建解码器,根据 输入流的 的编码信息创建
*/
AVCodecContext *decodec_ctx_create(AVFormatContext *ifmt_ctx, int stream_index)
{
	/*
	AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
		根据传入的参数 编解码信息codec,创建 codec上下文,统管整个 解码器
		如果传参为null,则创建 空的解码器,并初始化各个数值为默认
			后续必须要通过 其他方式 初始化 具体的解码器(比如h264 raw等)

			如果释放,请使用 avcodec_free_context3(ctx);
	*/
	AVCodecContext *decodec_ctx = avcodec_alloc_context3(NULL);	 
	
	/*
		avcodec_parameters_to_context(decodec_ctx,  video_codec)
		根据 输入流中的 编解码信息,初始化 decodec上下文的 解码信息,其实就是简单的拷贝而已

			AVCodecContext: 			 
				enum AVMediaType codec_type;	媒体类型(视频AVMEDIA_TYPE_VIDEO 音频AVMEDIA_TYPE_AUDIO)
				AVCodec *codec		; ###采用的解码器对象,ff提供					h264,raw,vp9...
				enum AVCodecID codec_id;	###标定特定的编解码器 id
				int format; 视频像素格式/音频采集格式	
				int width,height; ##视频宽和高
				int bit_rate	; 平均比特率 
				int channels;	声道数量
				int64 channel_layout; 声道格式
				int sample_rate;	音频采样率 
				AVRational time_base;	帧间隔	{1,30}		单位为sec
							framerate; 帧率  {30,1}
				char *extradata; int extrasize; 编解码器额外信息,h264用来store SPS PPS
				
				int refs：运动估计参考帧的个数
				int profile,level ; 	h264的参数
	*/
	avcodec_parameters_to_context(decodec_ctx, ifmt_ctx->streams[stream_index]->codecpar);
	/*
		经过上面的步骤,ctx->codec_id已经标识需要的解码器,下一步从ff系统中找出
		ff实现的解码器

		然后填充到 ctx中,从此 ctx掌握一切.
	*/
	const AVCodec *pDecodec = avcodec_find_decoder(decodec_ctx->codec_id);
    if(avcodec_open2(decodec_ctx, pDecodec,NULL)<0){
        printf("cannot decondec\n");
        return NULL ;
    }
	printf("decodec ctx pix fmt=%d\n",decodec_ctx->pix_fmt);
	return decodec_ctx;
}


AVCodecContext *encodec_ctx_create(int width, int height ) 
{
    int ret = 0;

	AVCodecContext *enc_ctx = NULL;
    //avcodec_register_all();

	/*
		AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
		指定编码器ID, 从系统中找出 ff已经实现的 编码器对象;
			也可以通过name找到  			codec = avcodec_find_encoder_by_name("libx264");
	*/
	const AVCodec * codec = avcodec_find_encoder(AV_CODEC_ID_H264);//找到系统实现的编码器
    if (!codec) {
        printf("Codec libx264 not found\n");
        exit(1);
    }
	/*
		AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
			根据从 ff找出的编码器,创建 编码器上下文;

		因为编码器,比如h264是我们创建的,我们需要初始化很多参数,比如压缩率 IPB帧  ,图像大小等
	*/
    enc_ctx = avcodec_alloc_context3(codec);
    if (!enc_ctx) {
        printf("Could not allocate video codec context!\n");
        exit(1);
    }
    //########## 	SPS/PPS相关参数  #########
    enc_ctx->profile = FF_PROFILE_H264_HIGH_444;	//h264的标准, HIGH全功能   ,MAIN大部分,BASE基础功能, 影响CPU性能
    enc_ctx->level = 50; 							//表示LEVEL是5.0  ,level指定压缩的均衡性,越高适用于 高分辨率 高帧率的情况
	enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO;		 
    enc_ctx->width = width;   // 640		分辨率
    enc_ctx->height = height; // 480
    enc_ctx->gop_size = 30;   //GOP: group of picture, I P P B P ...
    enc_ctx->keyint_min = 25; // option
    //设置B帧数据	 的数量
    enc_ctx->max_b_frames = 0; //  不要B帧
    enc_ctx->has_b_frames = 0; //  不要B帧
    //运动参考帧的数量
    enc_ctx->refs = 3; // option
    //设置输入YUV格式
    enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;		//指定要处理的视频格式,必须是420p/sp
    enc_ctx->bit_rate = 3000000; 				// 码率	3Mbps
    //设置帧率
    enc_ctx->time_base = (AVRational){1, V_HZ}; //帧与帧之间的间隔是time_base  ##需要改进,从输入源获取
    enc_ctx->framerate = (AVRational){V_HZ, 1}; //帧率，每秒 30帧
	enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;//添加 sps pps,每个GOP添加这些信息
    av_opt_set(enc_ctx->priv_data, "preset", "ultrafast", 0);    //快速编码，但会损失质量
    av_opt_set(enc_ctx->priv_data, "tune", "zerolatency", 0);  //适用于快速编码和低延迟流式传输,但是会出现绿屏

	/*再次使用codec 初始化ctx*/
    ret = avcodec_open2(enc_ctx , codec, NULL);
    if (ret < 0) {
        printf("Could not open codec: %s!\n", av_err2str(ret));
        exit(1);
    }
	
	return enc_ctx;
}

AVFormatContext *ofmt_ctx_create(char *push_addr,AVCodecContext *enc_ctx )
{
	AVFormatContext *ofmt_ctx;

	avformat_network_init();
	/*
		int avformat_alloc_output_context2(AVFormatContext **avctx, 
						const AVOutputFormat *oformat,
                                   const char *format, const char *filename)
			根据后面的参数,创建输出ctx
				oformat, format, filename 指定ctx的输出格式,  一般可以通过 文件后缀mp4 .h264等
					该函数,首先使用 oformat指定的 格式,如果没有 则使用 fomart(RTSP RTMP flv mpeg..)指定格式.
						如果也没有那么就 读取filename,也可以是url

				后面的参数,就是补充初始化完善 ,          	 AVOutputFormat *oformat;	

					AVFormatContext:
						char filename[]	,	保存的文件名 url[rtsp rtmp]等
						int nb_streams;		流个数
						AVStream **streams;  存放流的  地方
						struct AVInputFormat *iformat;		//输入流信息 
						struct AVOutputFormat *oformat;		//####输出流信息
								char *name; //格式名称
								char *long_name; 
								char *extensions; //扩展名
								enum AVCodecID audio_codec;  	
								enum AVCodecID video_codec;   					
								int flags;	//比如 AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS 	

						void *priv_data	; 存放一些私有设置	

			作为一个输出控制ctx,内部也有 不同的stream,需要我们手工创建
	*/
	
	int ret = avformat_alloc_output_context2(&ofmt_ctx, 0,  "RTSP", push_addr);
	if(ret){
		printf("%s-%d err\n",__func__,__LINE__);	return NULL;
	}
	
	av_opt_set(ofmt_ctx->priv_data, "rtsp_transport", "tcp", 0);	//需要改进,支持不同文件
	ofmt_ctx->max_interleave_delta = 1000000;		//缓冲区 最大延迟时间,默认1s,单位us

	/*
		我们只需要 视频流,创建
		流中需要包含 编解码信息 分辨率 帧率 图像格式等,这些东西都在 codec中有

		AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
			AVCodec *c: 创建流,需要的编解码器信息. 如果NULL则创建 空的stream,后面再次需要初始化
			AVFormatContext *s :  为s输出上下文创建stream,  
			函数返回 被创建的stream, 而且已经添加到 ofmt_ctx中.
	*/

	AVStream *pstream = avformat_new_stream(ofmt_ctx, NULL);
	if(!pstream){
		printf("%s-%d err\n",__func__,__LINE__); return NULL;
	}
	pstream->index = ofmt_ctx->nb_streams - 1;		//设置 流的id  0
	avcodec_parameters_from_context(pstream->codecpar, enc_ctx);	//从编码器复制参数
	/*初始化后,AVStream以下内容有了
	
		AVStream{	存储流媒体信息
			int index;	该媒体的id,编号 0 1 2 3 ...
			AVRational time_base;	时基,转换 PTS DTS,单位sec
			AVRational avg_frame_rate;	帧率,对视频重要
			AVCodecParameters  codecpar { 音视频参数,用来替换	上面启用的cdec
				enum AVMediaType codec_type // 媒体类型（视频AVMEDIA_TYPE_VIDEO，音频AVMEDIA_TYPE_AUDIO...）   
				enum AVCodecID codec_id // ###标示特定的编解码器（H.264,MPEG,raw ...）
				int format // 视频#像素#格式/音频采样数据格式
				int width, height // 表示视频的宽和高
				AVRational time_base; // 时基。通过该值可以把PTS，DTS转化为实际的时间（单位为秒s）
				........
			};
		}	
	*/

	pstream->codecpar->codec_tag = 0;		//codec的 附加信息,初始化为0
	//if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
	//	pstream->codecpar->flags |= CODEC_FLAG_GLOBAL_HEADER;
	printf("--ofmt new stream timebase num=%d den=%d duration=%ld\n",
		ofmt_ctx->streams[0]->time_base.num,ofmt_ctx->streams[0]->time_base.den,ofmt_ctx->streams[0]->duration);

	printf("-------ofmt_ctx print---------\n");
	av_dump_format(ofmt_ctx, 0, push_addr, 1);

	/* 
		输出ctx可能控制 文件,或者网络url.
		如果是文件则需要创建文件,  如何判断ofmt_ctx output 是否是上下文
	*/
	if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
		ret = avio_open(&ofmt_ctx->pb, push_addr , AVIO_FLAG_WRITE);
		if (ret != 0)	{
			av_error_output("avio_open err", ret);
			return NULL;
		}
	}
	
	ret = avformat_write_header(ofmt_ctx, NULL);// 写入封装头,头内部包含了 视频流的信息,包括压缩格式 分辨率 帧率 ...
	if (ret != 0){
		av_error_output("avformat_write_header", ret);	
		return NULL;
	}
	return ofmt_ctx;
}



int main(int argc, char *argv[]) 
{
    int ret = 0;

    AVFormatContext *ifmt_ctx = NULL;
	AVCodecContext  *decodec_ctx = NULL;//	for decodec camera to yuyv
	struct SwsContext *sws_ctx=NULL;
    AVCodecContext  *enc_ctx = NULL;
	AVFormatContext *ofmt_ctx = NULL;

    av_log_set_level(AV_LOG_WARNING);	//ffmpeg内部的库也会打印信息,指定打印级别

	/*第一步: 创建 输入上下文					
			之后,遍历,找出 ifmt_ctx中的 视频流编号,一般都是0
			因为 我们以后要使用 这个视频流
	*/
    ifmt_ctx = ifmt_ctx_create();
	int video_index = -1;
	for(int i = 0;i<ifmt_ctx->nb_streams;i++){
		if(  ifmt_ctx->streams[i]->codecpar->codec_type ==  AVMEDIA_TYPE_VIDEO){
			printf("ifmt video streamid %d pix format type %d\n",i, ifmt_ctx->streams[i]->codecpar->format );
			video_index=i;   break;		//记下来 输入上下文的 视频流的 编号
		}
	}
	if(video_index == -1 ){ printf("input ctx no stream\n");return -3;}
	
	/*
		第二步:	创建解码器 
		对ifmt中的 video stream 进行解码, 当然如何ifmt 是摄像头,则创建 空解码器
	*/
	decodec_ctx = decodec_ctx_create(ifmt_ctx,video_index);

	/*
		第三步: 创建图像格式转换上下文
    sws_ctx= sws_getContext(srcW,srcH,srcPixformat, 
    			dstW, dstH, desPixformat,  SWS_BICUBIC, NULL, NULL, NULL);
    	srcW,srcH,srcPixformat: 原始图像/未压缩的 宽度 高度 像素格式
    			可以从解码器中获取 .
    	dstW, dstH, desPixformat:目标原始图像的   ........	,需要自己指定
    	SWS_BICUBIC: 转换算法,还有SWS_FAST_BILINEAR ,SWS_BILINEAR 等

		后续可以直接使用它完成图像转换:	只适用于 裸数据,压缩后的肯定不行
			sws_scale(sws_ctx,  输入图像,输出图像 );
	*/
    sws_ctx = sws_getContext(decodec_ctx->width,decodec_ctx->height,decodec_ctx->pix_fmt,
    			decodec_ctx->width, decodec_ctx->height, AV_PIX_FMT_YUV420P,SWS_BICUBIC, NULL, NULL, NULL);
	printf("sws ctx switch from %dx%d fmt%d to %dx%d fmt %d\n",
			decodec_ctx->width, decodec_ctx->height , decodec_ctx->pix_fmt ,
						decodec_ctx->width, decodec_ctx->height ,AV_PIX_FMT_YUV420P);
    /*
		第四步:创建编码器上下文
			制定编码器h264的各种参数
	*/
    enc_ctx = encodec_ctx_create(decodec_ctx->width, decodec_ctx->height );
	/*
		第五步:创建输出上下文
	*/
	ofmt_ctx = ofmt_ctx_create(url_rtsp,enc_ctx);
	if(!ofmt_ctx){
		printf("open ofmt err\n");
		return 0;
	}


	/*
		AVPacket:	存储的是压缩的  数据和相关信息
			uint8_t *data;	压缩的编码数据 
				对于h264来说,一个  data对应一个NAL,有区别.可以直接写入文件,得到码流文件 
			int size;	数据大小
			int64  pts,dts;		显示时间卓,解码时间戳 
			int stream_index;	该数据包来自 输入上下文的 流id.		

		AVFrame:  存放非压缩数据 
 			uint8_t *data[ 4 ]; 	解码后原始数据,比如 YUV RGB PCM   [0]-Y  [1]-U ...
			int linesize[4];	 	各个色彩的大小,比如 [0]存放Y的大小
			int width,height;	
			int nb_smaples; 	音频的一个AVFrame可能包含多个帧
			int format; 解压后的类型 YUV420  YUVxxx  RGB 
			enum AVPictureType pict_type;	帧类型 I B P 
		注意,这些结构体 并没有包含实际视频数据的空间,还需要额外自己分配.
		当然 ff 在一些函数中也会帮你分配.
	*/
	AVPacket srcPkt;
	AVFrame *destFrame =   	av_frame_alloc() ; 
	AVFrame *pFrame420p =	av_frame_alloc() ; 
    AVPacket *destPkt = 	av_packet_alloc();
	
	/* 
		pFrame420p中的数据空间,需要自己手工分配;
			按照正常情况,是要分配3片大小不一样的空间存放yuv; 这里有了技巧
	*/
	char *out_buffer=(unsigned char *)av_malloc(  av_image_get_buffer_size(AV_PIX_FMT_YUV420P, decodec_ctx->width, decodec_ctx->height, 1 )); 
	av_image_fill_arrays(pFrame420p->data,pFrame420p->linesize,out_buffer, AV_PIX_FMT_YUV420P, decodec_ctx->width, decodec_ctx->height, 1 );
	
	/*
		从ifmt_ctx读取一帧数据,存入 srcPkt中
		int av_read_frame(AVFormatContext *ifmt_ctx, AVPacket *srcPkt)
			内部自动为srcPkt分配视频空间, 如果使用完毕 必须使用 av_packet_unref(srcPkt)释放
			否则内存泄漏.
			返回0-success  ,	negtive-err
	*/
    while (   av_read_frame(ifmt_ctx, &srcPkt) == 0   ) {
        int i = 0;
		ret = avcodec_send_packet(decodec_ctx,&srcPkt);		 
		if(ret){ av_error_output("send packet err", ret) ;return 0;}
		ret = avcodec_receive_frame(decodec_ctx,  destFrame);	//###自动分配空间,不要自己释放, 下次不需要分配
		if(ret){ av_error_output("recv frame err", ret) ;return 0;}
		// 422p to 420p
        sws_scale(sws_ctx,(const unsigned char* const*)destFrame->data, destFrame->linesize, 0,
                 decodec_ctx->height, pFrame420p->data, pFrame420p->linesize);  //根据前面配置的缩放参数，进行图像格式转换以及缩放等操作
		printf("src frame size %d %d %d dst size= %d %d %d w h=%d %d \n",
			destFrame->linesize[0],destFrame->linesize[1],destFrame->linesize[2],
			pFrame420p->linesize[0],pFrame420p->linesize[1],pFrame420p->linesize[2],
				pFrame420p->width,pFrame420p->height);	// w h 都是,需要自己初始化
		/*
			pFrame420p	只包含了 裸数据流,内部的视频信息 可能还没有,自己设置
		*/
		pFrame420p->width = V_WIDTH; pFrame420p->height = V_HEIGHT;
		pFrame420p->format = AV_PIX_FMT_YUV420P;  

		 static long long int pts_val = 0;
		 //##### 送420p给编码器进行编码 #######
		 ret = avcodec_send_frame(enc_ctx, pFrame420p);
		 if (ret < 0) {
			 av_error_output("send frame err",ret);
			 exit(1);
		 }

		 /*
			将数据推入ofmt_ctx的视频流中
				输出上下文和输入上下文,格式一样,都有视频流和音频流
				我们视频数据要写入到视频流中
		 */
		 int ofmt_ctx_video_stream_index = -1;	
		 for(int i=0;i<ofmt_ctx->nb_streams;i++){
			 if( ofmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
				 ofmt_ctx_video_stream_index = i;	 break;
			 }
		 }
		 
		 /*		从编码器encodec_ctx获取编码好的数据,存入destPkt
					int  avcodec_receive_packet(AVCodecContext *encodec_ctx, AVPacket *destPkt)
						destPkt中视频空间会被自动分配;使用完毕后要使用 av_packet_unref释放.

					注意, 因为有队列的存在,队列可能为空,也可能包含了多帧数据
						为空则立即返回,如果多帧则使用while 循环处理
		 */
		 while (ret >= 0) {
			 ret = avcodec_receive_packet(enc_ctx, destPkt);
			 //#####  如果编码器数据不足时会返回  EAGAIN,或者到数据尾时会返回 AVERROR_EOF
			 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
				 break; 
			 } else if (ret < 0) {
				 printf("Error, Failed in avcodec_receive_packet!\n");
				 exit(1);
			 }

			 /*
				将destPkt发送出去,通过 输出上下文
			 */
			 ret = av_interleaved_write_frame(ofmt_ctx, destPkt);
			 av_packet_unref(destPkt);
		 }
        av_packet_unref(&srcPkt); //   不执行 会有 内存泄漏.
    }

__ERROR:
    if (ifmt_ctx) {
        avformat_close_input(&ifmt_ctx);
    }

    av_log(NULL, AV_LOG_DEBUG, "finish!\n");
    return 0;
}
